---
title: Guide - Persistence
sidebar_label: Persistence
---

# Persistence

Persistence in Hyper-fetch allows you to store cache and request queue data across application sessions. This is
achieved by providing a custom storage implementation that can save and retrieve data from a persistent source like
`localStorage` or `AsyncStorage`. This guide will walk you through setting up persistence for both cache and queues.

:::secondary What you'll learn

1.  How to enable **data persistence** for caching and request queues.
2.  The difference between **synchronous** and **asynchronous** persistence storages.
3.  How to implement **cache persistence** to store data between sessions.
4.  How to use **queue persistence** for offline support.
5.  The **limitations** of using persistent queues in a browser environment.

:::

:::note

Persistence storages can only handle JSON-serializable data. Remember that all file objects need to be converted to a
format like Base64 to be stored correctly.

:::

---

## Cache Persistence

To enable cache persistence, you need to provide a custom storage implementation to the `Cache` instance. This storage
object must have `set`, `get`, `keys`, `delete`, and `clear` methods.

```typescript
// An example using MMKV for React Native
import { MMKV } from "react-native-mmkv";
import { Cache, Client, CacheStorageType } from "@hyper-fetch/core";

export const storage = new MMKV({ id: "my-key" });

const persistenceStorage: CacheStorageType = {
  set: (key, data) => {
    storage.set(key, JSON.stringify(data));
  },
  get: (key) => {
    const value = storage.getString(key);
    return value ? JSON.parse(value) : undefined;
  },
  keys: () => storage.getAllKeys(),
  delete: (key) => storage.delete(key),
  clear: () => storage.clearAll(),
};

export const client = new Client({
  url: "https://api.my-app.com",
  cache: (instance) =>
    new Cache(instance, {
      storage: persistenceStorage,
    }),
});
```

### Async Persistence Storages

For asynchronous storages like `IndexedDB`, you can use the `lazyStorage` option. This approach loads data on-demand,
which can improve performance by not loading the entire cache into memory at once. Data is fetched when `cache.get()` is
called, and components are updated once the async operation completes.

```typescript
// An example using idb-keyval for the browser
import { get, set, del, keys } from "idb-keyval";
import { Cache, Client, CacheAsyncStorageType } from "@hyper-fetch/core";

const asyncStorage: CacheAsyncStorageType = {
  set: (key, data) => set(key, data),
  get: (key) => get(key),
  keys: () => keys(),
  delete: (key) => del(key),
};

export const client = new Client({
  url: "https://api.my-app.com",
  cache: (instance) =>
    new Cache(instance, {
      lazyStorage: asyncStorage,
    }),
});
```

---

## Queue Persistence

Queue persistence ensures that requests are not lost if the application closes or loses internet connection. When the
application restarts, the dispatcher will attempt to resend any queued requests. To enable it, you need to provide a
synchronous storage implementation to the `Dispatcher`.

:::caution

Persistent queues are not recommended for browser environments due to concurrency issues between multiple tabs or
windows. This can lead to duplicated requests and potential data corruption. They are best suited for environments like
React Native where only a single instance of the application is running.

:::

### Fetching Request Persistence

You can make requests persistent by providing storage to the `fetchDispatcher`.

```typescript
import { MMKV } from "react-native-mmkv";
import { Client, Dispatcher, DispatcherStorageType } from "@hyper-fetch/core";

export const storage = new MMKV({ id: "my-key" });

const persistenceStorage: DispatcherStorageType = {
  set: (key, data) => {
    storage.set(key, JSON.stringify(data));
  },
  get: (key) => {
    const value = storage.getString(key);
    return value ? JSON.parse(value) : undefined;
  },
  keys: () => storage.getAllKeys(),
  delete: (key) => storage.delete(key),
  clear: () => storage.clearAll(),
};

export const client = new Client({
  url: "https://api.my-app.com",
  fetchDispatcher: (instance) =>
    new Dispatcher(instance, {
      storage: persistenceStorage,
    }),
});
```

### Submit Request Persistence

Similarly, requests sent to `submitDispatcher` can be persisted by providing storage to the `submitDispatcher`.

```typescript
import { MMKV } from "react-native-mmkv";
import { Client, Dispatcher, DispatcherStorageType } from "@hyper-fetch/core";

export const storage = new MMKV({ id: "my-key" });

const persistenceStorage: DispatcherStorageType = {
  set: (key, data) => {
    storage.set(key, JSON.stringify(data));
  },
  get: (key) => {
    const value = storage.getString(key);
    return value ? JSON.parse(value) : undefined;
  },
  keys: () => storage.getAllKeys(),
  delete: (key) => storage.delete(key),
  clear: () => storage.clearAll(),
};

export const client = new Client({
  url: "https://api.my-app.com",
  submitDispatcher: (instance) =>
    new Dispatcher(instance, {
      storage: persistenceStorage,
    }),
});
```

---

:::success Congratulations!

You've learned how to implement persistence in Hyper-fetch!

- You can enable **cache persistence** using synchronous or asynchronous storages.
- You know how to set up **queue persistence** for `fetchDispatcher` and `submitDispatcher`.
- You understand how to create **custom storage adapters** for persistence.
- You are aware of the **concurrency issues** with persistent queues in browsers.

:::
